﻿using System.Collections.Generic;
using System.Diagnostics;
using Jayden.Dll.Cards.Enums;
using System;
using System.Security.Cryptography;

namespace Jayden.Dll.Cards
{
	[DebuggerDisplay("{ToString()}")]
	public class CardList : List<Card>
	{
		public CardList(params Card[] cards) : base(cards) { }
		public CardList(IEnumerable<Card> cards) : base(cards) { }
		public CardList(ulong mask)
		{
			ulong currentMask = 0;
			for (int c = 0; c < 52; c++)
			{
				currentMask = ((ulong)1) << c;
				if ((mask & currentMask) != 0)
					Add(Card.Parse(currentMask));
			}
		}

		public static CardList New52CardsDeck() { return new CardList(Card.Cards); }

		public ulong Mask
		{
			get
			{
				ulong mask = 0;
				foreach (Card card in this)
					mask = mask | card.Mask;
				return mask;
			}
		}

		public static CardList Parse(string text) { return new CardList(Card.ParseArray(text)); }

		public override string ToString()
		{
			List<string> shorts = new List<string>();
			foreach (Card c in this)
				shorts.Add(c.ToShortString());
			return string.Join(" ", shorts);
		}

		public override int GetHashCode()
		{
			int hashCode = 0;
			foreach (Card card in this)
			{
				hashCode *= 31;
				hashCode += card.GetHashCode();
			}
			return hashCode;
		}

		public override bool Equals(object obj)
		{
			if (!(obj is CardList))
				return false;
			CardList other = obj as CardList;
			if (other.Count != Count)
				return false;
			for (int c = 0; c < other.Count; c++)
			{
				if (!this[c].Equals(other[c]))
					return false;
			}
			return true;
		}

		public CardList GetFirsts(uint count)
		{
			CardList firsts = new CardList();
			for (int c = 0; c < count && c < Count; c++)
				firsts.Add(this[c]);
			return firsts;
		}

		public void Shuffle(Guid guid1, Guid guid2)
		{
			byte[] b1 = guid1.ToByteArray();
			byte[] b2 = guid2.ToByteArray();

			byte[] all = new byte[b1.Length + b2.Length];
			Array.Copy(b1, all, b1.Length);
			Array.Copy(b2, 0, all, b1.Length, b2.Length);

			Shuffle(all);
		}
		public void Shuffle(byte[] bitseed)
		{
			if (bitseed == null || bitseed.Length != 32)
				throw new Exception("the seed must be 32bytes long.");
			int[] cardsToGet = new int[] { 5, 5, 6, 6, 6, 7, 8, 9 };

			List<Card> cards = new List<Card>(this);
			Clear();

			for (int c = 0; c < cardsToGet.Length; c++)
			{
				int seed = BitConverter.ToInt32(bitseed, c * 4);
				Random random = new Random(seed);
				for (int d = 0; d < cardsToGet[c]; d++)
				{
					int index = random.Next(cards.Count);
					Add(cards[index]);
					cards.RemoveAt(index);
				}
			}
		}
		public void Shuffle()
		{
			RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
			byte[] b32 = new byte[32];
			rng.GetBytes(b32);
			Shuffle(b32);
		}
		public void Shuffle(int seed)
		{
			Random rand = new Random(seed);
			Shuffle(rand);
		}
		public void Shuffle(Random rand)
		{
			
			List<Card> cards = new List<Card>(this);
			Clear();
			while (cards.Count > 0)
			{
				int index = rand.Next(cards.Count);
				Add(cards[index]);
				cards.RemoveAt(index);
			}
		}

		public void Burn() { Pop(); }
		public Card Pop()
		{
			if (Count == 0)
				throw new Exception("Empty CardList.");
			Card first = First;
			RemoveAt(0);
			return first;
		}
		public Card First { get { return this[0]; } }
	}

	[DebuggerDisplay("{ToString()}")]
	public class CardSet
	{
		private ulong m_Bitset = 0x00;

		public static readonly CardSet All = new CardSet(Card.Cards);

		public static readonly CardSet Spades = new CardSet(Card.GetSuits(CardSuit.Spades));
		public static readonly CardSet Hearts = new CardSet(Card.GetSuits(CardSuit.Hearts));
		public static readonly CardSet Diamonds = new CardSet(Card.GetSuits(CardSuit.Diamonds));
		public static readonly CardSet Clubs = new CardSet(Card.GetSuits(CardSuit.Clubs));

		public CardSet() {}
		public CardSet(ulong bitset) { m_Bitset = bitset; }
		public CardSet(CardSet set) { m_Bitset = set.m_Bitset; }
		public CardSet(params CardSet[] sets)
		{
			foreach (CardSet set in sets)
				m_Bitset |= set.m_Bitset;
		}
		public CardSet(IEnumerable<Card> cards)
		{
			foreach (Card card in cards)
				Add(card);
		}
		public CardSet(params Card[] cards)
		{
			foreach (Card card in cards)
				Add(card);
		}

		public static CardSet Parse(string text) { return new CardSet(Card.ParseArray(text)); }

		public ulong Bitset { get { return m_Bitset; } set { m_Bitset = (value & All.Bitset); } }

		public int Count
		{
			get
			{
				ulong i = m_Bitset;
				i = i - ((i >> 1) & 0x5555555555555555UL);
				i = (i & 0x3333333333333333UL) + ((i >> 2) & 0x3333333333333333UL);
				return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56);
			}
		}
		public bool Contains(Card card) { return (m_Bitset & card.Mask) != 0; }
		public void Add(Card card) { if (card != null) { m_Bitset |= card.Mask; } }
		public void Add(CardList cards)
		{
			foreach (Card card in cards)
				Add(card);
		}
		public void Remove(Card card) { m_Bitset &= (~card.Mask); }

		public void Add(CardSet set) { m_Bitset |= set.m_Bitset; }
		public void Remove(CardSet set) { m_Bitset &= (~set.m_Bitset); }
		public void Keep(CardSet set) { m_Bitset &= set.m_Bitset; }

		public IEnumerable<ulong> Masks
		{
			get
			{
				ulong mask = 1;
				ulong cursor = m_Bitset;
				while(cursor != 0)
				{
					if ((cursor & 0x01) != 0)
						yield return mask;
					mask = mask << 1;
					cursor = cursor >> 1;
				}
			}
		}

		public IEnumerable<Card> Cards
		{
			get
			{
				foreach (ulong mask in Masks)
					yield return Card.Parse(mask);
			}
		}

		public override string ToString()
		{
			List<string> shorts = new List<string>();
			foreach (Card card in Cards)
				shorts.Add(card.ToShortString());
			return string.Join(" ", shorts);
		}
	}
}
